package com.xiaouyudeguang.common.around;

import com.xiaouyudeguang.common.annotation.ApiMapping;
import com.xiaouyudeguang.common.aspect.AopAspect;
import com.xiaouyudeguang.common.cache.RedisCache;
import com.xiaouyudeguang.common.client.ClientInfo;
import com.xiaouyudeguang.common.config.ResourceServerConfig;
import com.xiaouyudeguang.common.constants.Headers;
import com.xiaouyudeguang.common.exception.BusinessException;
import com.xiaouyudeguang.common.utils.EnvironmentUtils;
import com.xiaouyudeguang.common.utils.SecurityUtils;
import com.xiaouyudeguang.common.utils.StringUtils;
import com.xiaouyudeguang.common.utils.UrlUtils;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.annotation.Order;
import org.springframework.security.authentication.AnonymousAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;

import javax.servlet.http.HttpServletRequest;

@Slf4j
@Aspect
@Component
@Order(Integer.MAX_VALUE - 1)
public class SecurityAround extends AopAspect {

    @Autowired
    private HttpServletRequest request;

    @Autowired(required = false)
    private TokenStore tokenStore;

    @Autowired
    private ResourceServerConfig resourceServerConfig;

    @Pointcut("execution(* com.*.*.*.resource..*.*(..)) || @annotation(annotation.io.gitee.xiaouyudeguang.common.ApiMapping)")
    public void security() {
    }

    @SneakyThrows
    @Around("security()")
    public Object around(ProceedingJoinPoint pjp) {
        if (RequestContextHolder.getRequestAttributes() == null) {
            return pjp.proceed();
        }
        RequestContextHolder.setRequestAttributes(RequestContextHolder.getRequestAttributes(), true);
        ClientInfo clientInfo = null;
        if (tokenStore == null) {
            clientInfo = resourceServerConfig.getClient();
            if (clientInfo == null) {
                throw new BusinessException(-System.currentTimeMillis());
            }
        }
        if ((EnvironmentUtils.isLocal() && StringUtils.isBlank(resourceServerConfig.getClientMode())) || UrlUtils.match(resourceServerConfig.getIgnoreUrls(), request.getRequestURI()) || (clientInfo != null && UrlUtils.match(clientInfo.getIgnoreUrls(), request.getRequestURI()))) {
            return pjp.proceed();
        }
        if (tokenStore == null && !(SecurityUtils.getAuthentication() instanceof AnonymousAuthenticationToken)) {
            return pjp.proceed();
        }
        super.initAspect(pjp);
        //接口没有ApiMapping注解当做一般接口允许访问
        ApiMapping apiMapping = AnnotationUtils.findAnnotation(realMethod, ApiMapping.class);
        if (tokenStore == null && (apiMapping == null || !apiMapping.isOpen())) {
            throw new BusinessException(-System.currentTimeMillis(), "访问受限");
        }
        //判断请求头的from参数是否在允许清单
        String from = request.getHeader(Headers.FROM);
        String appId = request.getHeader(Headers.APP_ID);
        if (StringUtils.isNotBlank(from) && from.equals(appId)) {
            return pjp.proceed();
        }
        String token = request.getHeader(Headers.AUTHORIZATION);
        if (StringUtils.isBlank(token)) {
            throw new BusinessException(-System.currentTimeMillis(), "Token not found");
        }
        token = token.replace(OAuth2AccessToken.BEARER_TYPE.toLowerCase(), StringUtils.EMPTY).trim();
        if (token.startsWith("share:")) {
            token = RedisCache.get(token);
        }
        if (StringUtils.isBlank(token)) {
            throw new BusinessException(-System.currentTimeMillis(), "Token not found");
        }
        OAuth2AccessToken accessToken = tokenStore.readAccessToken(token);
        if (accessToken == null || StringUtils.isBlank(accessToken.getValue())) {
            throw new BusinessException(-System.currentTimeMillis(), "Token was not recognised");
        }
        if (accessToken.isExpired()) {
            throw new BusinessException(-System.currentTimeMillis(), "Token was expired");
        }
        OAuth2Authentication oAuth2Authentication = (OAuth2Authentication) tokenStore.readAuthentication(token);
        if (oAuth2Authentication == null) {
            throw new BusinessException(-System.currentTimeMillis(), "Token was not recognised");
        }
        SecurityContextHolder.getContext().setAuthentication(oAuth2Authentication);
        request.setAttribute(Headers.USER_ID, SecurityUtils.getUserId());
        return pjp.proceed();
    }
}
